#include "pjsip/sip_auth_ssapi.h"

#define THIS_FILE   "sip_auth_ssapi.c"

#define str_cat_whith_char(str_dst,str_value,char_left,char_right)  \
	pj_strcat2(str_dst,char_left);\
	pj_strcat(str_dst,str_value);\
	pj_strcat2(str_dst,char_right);

#define str_cat2_whith_char(str_dst,char_value,char_left,char_right)  \
	pj_strcat2(str_dst,char_left);\
	pj_strcat2(str_dst,char_value);\
	pj_strcat2(str_dst,char_right);


/*
*	negatiate one cached_auth
*/
PJ_DECL(pj_status_t) pjsip_auth_ntlm_negotiate(pj_pool_t *pool,pjsip_ntlm_credential *cred,const pjsip_ntlm_challenge *chal,const pjsip_cred_info *cred_info,pjsip_cached_auth *pcached_auth)
	{
	pj_str_t gssi_data={0,0};
	pj_str_t out_gssi_data = {0,0};
	pj_int32_t len;
	pj_pool_t *temp_pool = NULL;
	pj_status_t ss;
	temp_pool = pj_pool_create(pool->factory,"auth_temp_pool",256,256,NULL);

	pj_assert(pcached_auth->auth_status!=AUTH_STATUS_INVALID);

	if (pcached_auth->auth_status == AUTH_STATUS_ESTABLISHING||pcached_auth->auth_status == AUTH_STATUS_IDLE)
		{
		if (chal->gssapi_data.slen)
			{
			len = PJ_BASE64_TO_BASE256_LEN(chal->gssapi_data.slen)+1;
			gssi_data.ptr = (char *)pj_pool_zalloc(temp_pool,len);
			gssi_data.slen = len;
			ss =   pj_base64_decode(&chal->gssapi_data,(pj_uint8_t*)gssi_data.ptr,&len);
			gssi_data.slen = len;
			if (ss!=PJ_SUCCESS)
				{
				goto error_handle;
				}
			}
		if (!pj_strcmp(&cred_info->scheme,&pjsip_NTLM_STR))
			{
			pj_strdup(pool,&cred->realm,&pcached_auth->realm);
			pj_strdup(pool,&cred->targetName,&pcached_auth->targetname);

			//only in AUTH_STATUS_ESTABLISHING(so called sencond negotiate) status add these fields!
			if (pcached_auth->auth_status == AUTH_STATUS_ESTABLISHING)
				{
				cred->crand.ptr = pj_pool_zalloc(pool,8);
				cred->crand.slen = 8; 
				pj_strdup(pool,&cred->opaque,&pcached_auth->opaque);
				pj_create_random_string(cred->crand.ptr,8);
				cred->cnum.ptr = pj_pool_zalloc(pool,8);
				cred->cnum.slen = pj_utoa(++pcached_auth->cnum,cred->cnum.ptr);
				}
			cred->version = 4;
			cred->qop = pj_str("auth");

			if (genClientResponse(temp_pool,cred_info->scheme,cred_info->username,cred_info->password,cred_info->domain,pcached_auth->targetname,gssi_data,&pcached_auth->hcred,&pcached_auth->hctxe,&out_gssi_data,&pcached_auth->maxSignLength))
				{
				if (pcached_auth->auth_status == AUTH_STATUS_IDLE)
					{
					pcached_auth->auth_status = AUTH_STATUS_ESTABLISHING;
					}
				//  the gssi_data send to server ,if not null , convert it from bytes to base64
				if (out_gssi_data.slen)
					{
					len = PJ_BASE256_TO_BASE64_LEN(out_gssi_data.slen)+1;
					cred->gssapi_data.ptr = (char*)pj_pool_zalloc(pool,len);
					cred->gssapi_data.slen = len;
					pj_base64_encode((pj_uint8_t*)out_gssi_data.ptr,out_gssi_data.slen,cred->gssapi_data.ptr,&len);
					cred->gssapi_data.slen = len;
					}
				}
			else
				{	
				goto error_handle;
				}
			}
		else
			{
			pj_assert(!"do not support other protocol!");
			//other protocol
			}
		}

error_handle:
	if (temp_pool!=NULL)
		{
		pj_pool_release(temp_pool);
		temp_pool = NULL;
		}
	return ss;
	}

PJ_DECL(pj_status_t) pjsip_auth_negotiate(pjsip_cached_auth * pcached_auth,pjsip_tx_data *tdata)
	{

	pjsip_proxy_authorization_hdr *proxy_auth_hdr = NULL;
	pj_str_t gssi_data;
	pj_str_t out_gssi_data;
	pj_int32_t len;
	pj_pool_t *temp_pool = NULL;
	pj_status_t ss;

	pj_assert(pcached_auth->auth_status==AUTH_STATUS_IDLE||pcached_auth->auth_status == AUTH_STATUS_ESTABLISHING);

	temp_pool = pj_pool_create(tdata->pool->factory,"auth_temp_pool",256,256,NULL);

	//if server response gssidata is not null , convert it to base64 code
	if (pcached_auth->gssapidata.slen)
		{
		len = PJ_BASE64_TO_BASE256_LEN(pcached_auth->gssapidata.slen);
		gssi_data.ptr = (char *)pj_pool_zalloc(temp_pool,len);
		gssi_data.slen = len;
		ss =   pj_base64_decode(pcached_auth->gssapidata.ptr,(pj_uint8_t*)gssi_data.ptr,&len);
		gssi_data.slen = len;
		if (ss!=PJ_SUCCESS)
			{
			goto error_handle;
			}
		}
	//status must be AUTH_STATUS_IDLE or AUTH_STATUS_ESTABLISHING

	proxy_auth_hdr = pjsip_proxy_authorization_hdr_create(tdata->pool);
	if (NULL==proxy_auth_hdr)
		{
		ss = PJ_ENOMEM;
		goto error_handle;
		}
	pj_strdup(tdata->pool,&proxy_auth_hdr->scheme,&pcached_auth->cred_info.scheme);
	//if the NTLM authentication
	if (!pj_strcmp(&pcached_auth->cred_info.scheme,&pjsip_NTLM_STR))
		{

		pj_strdup(tdata->pool,&proxy_auth_hdr->credential.ntlm.realm,&pcached_auth->realm);
		pj_strdup(tdata->pool,&proxy_auth_hdr->credential.ntlm.targetName,&pcached_auth->targetname);

		//only in AUTH_STATUS_ESTABLISHING(so called sencond negotiate) status add these fields!
		if (pcached_auth->auth_status == AUTH_STATUS_ESTABLISHING)
			{
			proxy_auth_hdr->credential.ntlm.crand.ptr = pj_pool_zalloc(tdata->pool,8);
			proxy_auth_hdr->credential.ntlm.crand.slen = 8; 
			pj_strdup(tdata->pool,&proxy_auth_hdr->credential.ntlm.opaque,&pcached_auth->opaque);
			pj_create_random_string(proxy_auth_hdr->credential.ntlm.crand.ptr,8);


			proxy_auth_hdr->credential.ntlm.cnum.slen = pj_utoa(++pcached_auth->cnum,proxy_auth_hdr->credential.ntlm.cnum.ptr);
			}
		proxy_auth_hdr->credential.ntlm.version = 4;
		proxy_auth_hdr->credential.ntlm.qop = pj_str("auth");

		if (genClientResponse(temp_pool,pcached_auth->cred_info.scheme,pcached_auth->cred_info.username,pcached_auth->cred_info.password,pcached_auth->cred_info.domain,pcached_auth->targetname,gssi_data,&pcached_auth->hcred,&pcached_auth->hctxe,&out_gssi_data,&pcached_auth->maxSignLength))
			{
			if (pcached_auth->auth_status == AUTH_STATUS_IDLE)
				{
				pcached_auth->auth_status = AUTH_STATUS_ESTABLISHING;
				}
			//  the gssi_data send to server ,if not null , convert it from bytes to base64
			if (out_gssi_data.slen)
				{
				len = PJ_BASE256_TO_BASE64_LEN(out_gssi_data.slen);
				proxy_auth_hdr->credential.ntlm.gssapi_data.ptr = pj_pool_zalloc(tdata->pool,len);
				proxy_auth_hdr->credential.ntlm.gssapi_data.slen = len;
				pj_base64_encode((pj_uint8_t*)out_gssi_data.ptr,out_gssi_data.slen,proxy_auth_hdr->credential.ntlm.gssapi_data.ptr,&len);
				proxy_auth_hdr->credential.ntlm.gssapi_data.slen = len;
				}
			}
		else
			{	
			goto error_handle;
			}
		}
	else
		{
		pj_assert(!"current do not support other protocol!");
		//other protocol
		}

	// add proxy_auth_hdr to msg 
	pj_list_insert_before(&tdata->msg->hdr,proxy_auth_hdr);
	pj_pool_release(temp_pool);
	return PJ_SUCCESS;

error_handle:
	if (temp_pool!=NULL)
		{
		pj_pool_release(temp_pool);
		temp_pool = NULL;
		}
	return ss;
	}


PJ_DECL(pj_status_t) pjsip_auth_verify_signature(pjsip_rx_data *rdata)
	{

	}



/*
*	use auth_sess to make signature for every authorization header in outbound msg; 
*/
PJ_DECL(pj_status_t) pjsip_auth_make_signature(pjsip_auth_clt_sess * auth_sess,pjsip_tx_data *tdata)
	{
	pjsip_proxy_authorization_hdr *hdr = NULL;
	pjsip_auth_clt_sess * sess = auth_sess;
	pjsip_cred_info *cred_info = NULL;
	unsigned i;
	pj_str_t sign_data={0,0};
	pj_status_t status;
	pjsip_cached_auth * cached_auth = NULL;
	pj_pool_t *pool = auth_sess->pool;
	
	cached_auth = sess->cached_auth.next;
	while (cached_auth!=&sess->cached_auth)
			{
			if (cached_auth->auth_status == AUTH_STATUS_IDLE||cached_auth->auth_status == AUTH_STATUS_INVALID)
				{
				cached_auth = cached_auth->next;
				continue;
				}
			if (cached_auth->auth_status == AUTH_STATUS_ESTABLISHED)
				{
					hdr = pjsip_proxy_authorization_hdr_create(tdata->pool);
					if (!pj_strcmp(&cached_auth->scheme,&pjsip_NTLM_STR))
					{
					    //common and const
						pj_strdup(tdata->pool,&hdr->scheme,&cached_auth->scheme);
						pj_strdup(tdata->pool,&hdr->credential.ntlm.realm,&cached_auth->realm);
						hdr->credential.ntlm.qop = pj_str("auth");
						hdr->credential.ntlm.version = 4;
						
						//cnum and crand
						hdr->credential.ntlm.crand.ptr = pj_pool_zalloc(pool,8);
						hdr->credential.ntlm.crand.slen = 8; 
						pj_strdup(tdata->pool,&hdr->credential.ntlm.opaque,&cached_auth->opaque);
						pj_create_random_string(hdr->credential.ntlm.crand.ptr,8);
						hdr->credential.ntlm.cnum.ptr = pj_pool_zalloc(pool,8);
						hdr->credential.ntlm.cnum.slen = pj_utoa(++cached_auth->cnum,hdr->credential.ntlm.cnum.ptr);
						
						//targetname
						pj_strdup(tdata->pool,&hdr->credential.ntlm.targetName,&cached_auth->targetname);
						
						status = get_tosign_str(tdata->pool,tdata->msg,&sign_data,hdr);
						if (status != PJ_SUCCESS)
						{
							PJ_LOG(1,(THIS_FILE,"get sign fails"));
							return status;
						}
						status = signString(tdata->pool,cached_auth,&sign_data,&hdr->credential.ntlm.response);
						if (status != PJ_SUCCESS)
						{
							PJ_LOG(1,(THIS_FILE,"make sign fails"));
							return status;
						}
						pjsip_msg_add_hdr(tdata->msg,hdr);
						//
					}
					else
					{
						pj_assert(!"have not support other auth protocol");
					}
				}
			else if (cached_auth->auth_status == AUTH_STATUS_ESTABLISHING&&cached_auth->opaque.slen)
				{
					//find anthorization header
					hdr = pjsip_msg_find_hdr(tdata->msg,PJSIP_H_PROXY_AUTHORIZATION,NULL);
					if (hdr==NULL)
					{
						hdr = pjsip_msg_find_hdr(tdata->msg,PJSIP_H_AUTHORIZATION,NULL);
						if (hdr ==NULL)
						{
							cached_auth = cached_auth->next;
							continue;
						}

					}
					status = get_tosign_str(tdata->pool,tdata->msg,&sign_data,hdr);
					if (status != PJ_SUCCESS)
						{
						PJ_LOG(1,(THIS_FILE,"get sign fails"));
						return status;
						}
					status = signString(tdata->pool,cached_auth,&sign_data,&hdr->credential.ntlm.response);
					if (status != PJ_SUCCESS)
						{
						PJ_LOG(1,(THIS_FILE,"make sign fails"));
						return status;
						}
				}
			cached_auth = cached_auth->next;
			}
	return PJ_SUCCESS;
	}
	static pj_bool_t genClientResponse(pj_pool_t *pool,pj_str_t authProtocol,pj_str_t username,pj_str_t password, pj_str_t domain,pj_str_t targetname,pj_str_t challege,CredHandle *hcred,SecHandle *hctxe,pj_str_t *outputResponse,pj_int32_t *maxSignLength)
		{
		unsigned long maxSign;
		SECURITY_STATUS   ss;
		TimeStamp         Lifetime;
		SecBufferDesc     OutBuffDesc;
		SecBuffer         OutSecBuff;
		SecBufferDesc     InBuffDesc;
		SecBuffer         InSecBuff;

		SecPkgInfo        *PkgInfo=NULL;
		ULONG             ContextAttributes;
		SEC_WINNT_AUTH_IDENTITY auth_identity;

		TCHAR      pTargetName[50]={0};
		TCHAR      pAuthProtocol[20]={0};

		pj_memcpy(pAuthProtocol,authProtocol.ptr,authProtocol.slen);
		pj_memcpy(pTargetName,targetname.ptr,targetname.slen);
		//first challege : 1. AcquireCredentialsHandle
		if (!challege.slen)
			{

			//pj_memcpy(pAuthPro,authProtocol.ptr,authProtocol.slen);
			auth_identity.Domain=domain.ptr;
			auth_identity.DomainLength = domain.slen;
			auth_identity.Password = password.ptr;
			auth_identity.PasswordLength = password.slen;
			auth_identity.User = username.ptr;
			auth_identity.UserLength = username.slen;
			auth_identity.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
			ss = AcquireCredentialsHandle(NULL,pAuthProtocol,SECPKG_CRED_OUTBOUND,NULL,&auth_identity,NULL,NULL,hcred,&Lifetime);

			if (!(ss>=0))
				{
				PJ_LOG(1,(THIS_FILE,"acquire credentials error"));
				return PJ_FALSE;
				}
			}



		//acquire output maxlength
		ss = QuerySecurityPackageInfo(pAuthProtocol,&PkgInfo);
		if (!(ss>=0))
			{
			return PJ_FALSE;
			}
		else
			{//init ouput buf
			outputResponse->ptr = pj_pool_zalloc(pool,PkgInfo->cbMaxToken);
			outputResponse->slen = 0;
			OutBuffDesc.ulVersion = 0;
			OutBuffDesc.cBuffers  = 1;
			OutBuffDesc.pBuffers  = &OutSecBuff;
			OutSecBuff.cbBuffer   = PkgInfo->cbMaxToken;
			OutSecBuff.BufferType = SECBUFFER_TOKEN;
			OutSecBuff.pvBuffer   = outputResponse->ptr;
			FreeContextBuffer(PkgInfo);
			}


		if (challege.slen)
			{
			InBuffDesc.ulVersion = 0;
			InBuffDesc.cBuffers  = 1;
			InBuffDesc.pBuffers  = &InSecBuff;

			InSecBuff.cbBuffer   = challege.slen;
			InSecBuff.BufferType = SECBUFFER_TOKEN;
			InSecBuff.pvBuffer   =(void*)challege.ptr; 

			ss = InitializeSecurityContext(hcred,hctxe,pTargetName,ISC_REQ_IDENTIFY|ISC_REQ_INTEGRITY|ISC_REQ_DATAGRAM|ISC_REQ_MUTUAL_AUTH
				,0,SECURITY_NATIVE_DREP,&InBuffDesc,0,hctxe,&OutBuffDesc,&ContextAttributes,&Lifetime);
			if (!(ss>=0))
				{
				return PJ_FALSE;
				}
			}
		else
			{

			SecPkgContext_Sizes contextSize;
			ss = InitializeSecurityContext(hcred,NULL,pTargetName,ISC_REQ_IDENTIFY|ISC_REQ_INTEGRITY|ISC_REQ_DATAGRAM|ISC_REQ_MUTUAL_AUTH
				,0,SECURITY_NATIVE_DREP,NULL,0,hctxe,&OutBuffDesc,&ContextAttributes,&Lifetime);
			if (!(ss>=0))
				{
				return PJ_FALSE;
				}
			if (*maxSignLength==0)
				{

				ss = QueryContextAttributes(hctxe,0,&contextSize);
				if (!(ss>=0))
					{
					return PJ_FALSE;
					}
				else
					{
					*maxSignLength = contextSize.cbMaxSignature;
					}
				}
			}
		outputResponse->slen = (pj_ssize_t)(OutSecBuff.cbBuffer);
		return PJ_TRUE;
		}

	static pj_status_t signString(pj_pool_t *pool,const pjsip_cached_auth *pcached_auth,const pj_str_t *strToSign,pj_str_t *signatureStr)
		{

		SECURITY_STATUS ss;
		SecBufferDesc inbuffdesc;
		SecBuffer     pinbuff[2];
		pj_int32_t len_base64_out,len_token;
		pj_uint8_t * buf_temp;
		
		pj_str_t buf_token;



		buf_temp = (pj_uint8_t*)pj_pool_zalloc(pool,pcached_auth->maxSignLength);



		pinbuff[0].BufferType = SECBUFFER_TOKEN;
		pinbuff[0].cbBuffer = pcached_auth->maxSignLength;
		pinbuff[0].pvBuffer = (void*)(buf_temp);

		pinbuff[1].BufferType = SECBUFFER_DATA;
		pinbuff[1].cbBuffer = strToSign->slen;
		pinbuff[1].pvBuffer = (void*)(strToSign->ptr);

		inbuffdesc.cBuffers = 2;
		inbuffdesc.pBuffers = pinbuff;
		inbuffdesc.ulVersion=0;

		ss =  MakeSignature(&pcached_auth->hctxe,0,&inbuffdesc,pcached_auth->signseqno);
		if (!(ss>=0))
			{
			return PJ_EUNKNOWN;
			}
		len_token = (pj_int32_t)(pinbuff[0].cbBuffer);
		buf_token.ptr = (char*)buf_temp;
		buf_token.slen = len_token;
		
		*signatureStr = pj_str_to_hex(pool,buf_token);
		if (!(ss>=0))
			{
			return ss;
			}
		return PJ_SUCCESS;
		}


	static pj_status_t get_tosign_str(pj_pool_t *pool,const pjsip_msg *msg,pj_str_t *toSignStr,pjsip_authorization_hdr *auth_hdr)
		{
		unsigned len = 0;
		pjsip_from_hdr *hdr_from;
		pjsip_to_hdr *hdr_to;
		pjsip_expires_hdr *hdr_expires;
		pjsip_cseq_hdr *hdr_cseq;
		pjsip_cid_hdr *hdr_cid;
		pjsip_authorization_hdr *hdr_proxy_hdr;
		pjsip_sip_uri *uri_from_hdr;
		pjsip_sip_uri *uri_to_hdr;

		char str_from_expire[8] = {0};
		char str_from_cseq[8] = {0};
		char str_from_status_code[8] = {0};


		hdr_from = (pjsip_from_hdr*)pjsip_msg_find_hdr(msg,PJSIP_H_FROM,NULL);
		hdr_to = (pjsip_to_hdr*)pjsip_msg_find_hdr(msg,PJSIP_H_TO,NULL);
		hdr_expires = (pjsip_expires_hdr*)pjsip_msg_find_hdr(msg,PJSIP_H_EXPIRES,NULL);
		hdr_cseq = (pjsip_cseq_hdr*)pjsip_msg_find_hdr(msg,PJSIP_H_CSEQ,NULL);
		hdr_cid = (pjsip_cid_hdr*)pjsip_msg_find_hdr(msg,PJSIP_H_CALL_ID,NULL);

		//other? www_auth?
		hdr_proxy_hdr=auth_hdr;

		pj_assert(hdr_from!=NULL&&hdr_to!=NULL&&hdr_cseq!=NULL&&hdr_cid!=NULL&&hdr_proxy_hdr!=NULL);
		
		uri_from_hdr = ((pjsip_sip_uri*)pjsip_uri_get_uri(hdr_from->uri));
		uri_to_hdr = ((pjsip_sip_uri*)pjsip_uri_get_uri(hdr_to->uri));
		len += uri_from_hdr->host.slen+uri_from_hdr->user.slen+1;
		len += uri_to_hdr->host.slen+uri_to_hdr->user.slen +1;
		len+= hdr_from->tag.slen;
		len+= hdr_to->tag.slen;
		len+= hdr_expires==NULL?0:pj_utoa(hdr_expires->ivalue,str_from_expire);
		len+= pj_utoa(hdr_cseq->cseq,str_from_cseq);
		len+= hdr_cseq->method.name.slen;
		len+= hdr_cid->id.slen;
		len+= hdr_proxy_hdr->scheme.slen;
		if (!pj_strcmp(&hdr_proxy_hdr->scheme,&pjsip_NTLM_STR))
			{
			len+= hdr_proxy_hdr->credential.ntlm.cnum.slen;
			len+=hdr_proxy_hdr->credential.ntlm.crand.slen;
			len+=hdr_proxy_hdr->credential.ntlm.realm.slen;
			len+=hdr_proxy_hdr->credential.ntlm.targetName.slen;
			}
		else
			{
			pj_assert(!"have not support other protocol");
			}
		//to add  PAssertedIdentityHeader  and PPreferredIdentityHeaders

		//if msg is response ,add status code
		if (msg->type == PJSIP_RESPONSE_MSG)
			{
			len+=pj_utoa((unsigned long)(msg->line.status.code),str_from_status_code);

			//"<>".length;
			len+=2;
			}
		// every element need "<>"
		len+=15*2+8;

		toSignStr->ptr = pj_pool_zalloc(pool,len);
		toSignStr->slen = 0;

		str_cat_whith_char(toSignStr,&hdr_proxy_hdr->scheme,"<",">");

		if (!pj_strcmp(&hdr_proxy_hdr->scheme,&pjsip_NTLM_STR))
			{
			str_cat_whith_char(toSignStr,&hdr_proxy_hdr->credential.ntlm.crand,"<",">");
			str_cat_whith_char(toSignStr,&hdr_proxy_hdr->credential.ntlm.cnum,"<",">");
			str_cat_whith_char(toSignStr,&hdr_proxy_hdr->credential.ntlm.realm,"<",">");
			str_cat_whith_char(toSignStr,&hdr_proxy_hdr->credential.ntlm.targetName,"<",">");
			}
		else
			{
			pj_assert(!"have not support other protocol");
			}
		str_cat_whith_char(toSignStr,&hdr_cid->id,"<",">");
		str_cat2_whith_char(toSignStr,str_from_cseq,"<",">");
		str_cat_whith_char(toSignStr,&hdr_cseq->method.name,"<",">");
		pj_strcat2(toSignStr,"<sip:");
		pj_strcat(toSignStr,&uri_from_hdr->user);
		pj_strcat2(toSignStr,"@");
		pj_strcat(toSignStr,&uri_from_hdr->host);
		pj_strcat2(toSignStr,">");
		str_cat_whith_char(toSignStr,&hdr_from->tag,"<",">");
		pj_strcat2(toSignStr,"<sip:");
		pj_strcat(toSignStr,&uri_to_hdr->user);
		pj_strcat2(toSignStr,"@");
		pj_strcat(toSignStr,&uri_to_hdr->host);
		pj_strcat2(toSignStr,">");
		str_cat_whith_char(toSignStr,&hdr_to->tag,"<",">");

		//to add  PAssertedIdentityHeader  and PPreferredIdentityHeaders
			{
			str_cat2_whith_char(toSignStr,"","<",">");
			str_cat2_whith_char(toSignStr,"","<",">");
			}
			str_cat2_whith_char(toSignStr,str_from_expire,"<",">");


			if (msg->type == PJSIP_RESPONSE_MSG)
				{
				str_cat2_whith_char(toSignStr,str_from_status_code,"<",">");
				}
			toSignStr->slen = len;
			return PJ_SUCCESS;
		}